/**
 * Copyright (C) 2010-2016 eBusiness Information, Excilys Group
 * Copyright (C) 2016-2020 the AndroidAnnotations project
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.ohosannotations.holder;

import com.helger.jcodemodel.AbstractJClass;
import com.helger.jcodemodel.IJAssignmentTarget;
import com.helger.jcodemodel.IJExpression;
import com.helger.jcodemodel.JBlock;
import com.helger.jcodemodel.JDefinedClass;
import com.helger.jcodemodel.JDirectClass;
import com.helger.jcodemodel.JExpr;
import com.helger.jcodemodel.JFieldRef;
import com.helger.jcodemodel.JFieldVar;
import com.helger.jcodemodel.JInvocation;
import com.helger.jcodemodel.JMethod;
import com.helger.jcodemodel.JSwitch;
import com.helger.jcodemodel.JVar;

import org.ohosannotations.OhosAnnotationsEnvironment;
import org.ohosannotations.api.bean.BeanHolder;
import org.ohosannotations.api.view.HasViews;
import org.ohosannotations.api.view.OnViewChangedListener;
import org.ohosannotations.api.view.OnViewChangedNotifier;
import org.ohosannotations.helper.CanonicalNameConstants;
import org.ohosannotations.internal.helper.ViewNotifierHelper;

import java.util.HashMap;
import java.util.Map;

import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;

import static com.helger.jcodemodel.JExpr._new;
import static com.helger.jcodemodel.JExpr._null;
import static com.helger.jcodemodel.JExpr._this;
import static com.helger.jcodemodel.JExpr.cast;
import static com.helger.jcodemodel.JExpr.invoke;
import static com.helger.jcodemodel.JMod.FINAL;
import static com.helger.jcodemodel.JMod.PRIVATE;
import static com.helger.jcodemodel.JMod.PUBLIC;

/**
 * ecomponent视图支持架
 *
 * @author dev
 * @since 2021-07-22
 */
public abstract class EComponentWithViewSupportHolder extends EComponentHolder implements HasKeyEventCallbackMethods {
    private static final String STRING_T = "T";
    /**
     * 查看通知助手
     */
    protected ViewNotifierHelper viewNotifierHelper;
    /**
     * 发现持有者
     */
    protected Map<String, FoundHolder> foundHolders = new HashMap<>();
    /**
     * 数据绑定委托
     */
    protected DataBindingDelegate dataBindingDelegate;
    /**
     * findSupportFragmentById
     */
    protected JMethod findSupportFragmentById;
    /**
     * findNativeFragmentByTag
     */
    protected JMethod findNativeFragmentByTag;
    private JMethod onViewChanged;
    private JBlock onViewChangedBody;
    private JBlock onViewChangedBodyInjectionBlock;
    private JBlock onViewChangedBodyViewHolderBlock;
    private JBlock onViewChangedBodyAfterInjectionBlock;
    private JBlock onViewChangedBodyBeforeInjectionBlock;
    private JVar onViewChangedHasViewsParam;

    private Map<String, TextObserverHolder> textObserverHolders = new HashMap<>();
    private Map<String, OnSliderProgressUpdatedListenerHolder> onSliderProgressUpdatedListenerHolders = new HashMap<>();
    private Map<String, PageChangeHolder> pageChangeHolders = new HashMap<>();
    private KeyEventCallbackMethodsDelegate<EComponentWithViewSupportHolder> keyEventCallbackMethodsDelegate;

    /**
     * EComponentWithViewSupportHolder
     *
     * @param environment environment
     * @param annotatedElement annotatedElement
     * @throws Exception
     */
    public EComponentWithViewSupportHolder(OhosAnnotationsEnvironment environment,
        TypeElement annotatedElement) throws Exception {
        super(environment, annotatedElement);
        viewNotifierHelper = new ViewNotifierHelper(this, environment);
        keyEventCallbackMethodsDelegate = new KeyEventCallbackMethodsDelegate<>(this);
        dataBindingDelegate = new DataBindingDelegate(this);
    }

    /**
     * getFindViewByIdExpression
     *
     * @param idParam idParam
     * @return onViewChangedBody
     */
    public IJExpression getFindViewByIdExpression(JVar idParam) {
        return _null();
    }

    /**
     * 视图改变了身体
     *
     * @return {@link JBlock}
     */
    public JBlock getOnViewChangedBody() {
        if (onViewChangedBody == null) {
            setOnViewChanged();
        }
        return onViewChangedBody;
    }

    /**
     * getOnViewChangedBodyBeforeInjectionBlock
     *
     * @return onViewChangedBodyBeforeInjectionBlock
     */
    public JBlock getOnViewChangedBodyBeforeInjectionBlock() {
        if (onViewChangedBodyBeforeInjectionBlock == null) {
            setOnViewChanged();
        }
        return onViewChangedBodyBeforeInjectionBlock;
    }

    /**
     * getOnViewChangedBodyInjectionBlock
     *
     * @return onViewChangedBodyInjectionBlock
     */
    public JBlock getOnViewChangedBodyInjectionBlock() {
        if (onViewChangedBodyInjectionBlock == null) {
            setOnViewChanged();
        }
        return onViewChangedBodyInjectionBlock;
    }

    /**
     * getOnViewChangedBodyViewHolderBlock
     *
     * @return getOnViewChangedBodyInjectionBlock
     */
    public JBlock getOnViewChangedBodyViewHolderBlock() {
        if (onViewChangedBodyViewHolderBlock == null) {
            setOnViewChanged();
        }
        return onViewChangedBodyViewHolderBlock;
    }

    /**
     * getOnViewChangedBodyAfterInjectionBlock
     *
     * @return onViewChangedBodyAfterInjectionBlock
     */
    public JBlock getOnViewChangedBodyAfterInjectionBlock() {
        if (onViewChangedBodyAfterInjectionBlock == null) {
            setOnViewChanged();
        }
        return onViewChangedBodyAfterInjectionBlock;
    }

    /**
     * getOnViewChangedHasViewsParam
     *
     * @return onViewChangedHasViewsParam
     */
    public JVar getOnViewChangedHasViewsParam() {
        if (onViewChangedHasViewsParam == null) {
            setOnViewChanged();
        }
        return onViewChangedHasViewsParam;
    }

    /**
     * setOnViewChanged
     */
    protected void setOnViewChanged() {
        getGeneratedClass()._implements(OnViewChangedListener.class);
        onViewChanged = getGeneratedClass().method(PUBLIC, getCodeModel().VOID, "onViewChanged");
        onViewChanged.annotate(Override.class);
        onViewChangedBody = onViewChanged.body();
        onViewChangedBodyBeforeInjectionBlock = onViewChangedBody.blockVirtual();
        onViewChangedBodyViewHolderBlock = onViewChangedBody.blockVirtual();
        onViewChangedBodyInjectionBlock = onViewChangedBody.blockVirtual();
        onViewChangedBodyAfterInjectionBlock = onViewChangedBody.blockVirtual();
        onViewChangedHasViewsParam = onViewChanged.param(HasViews.class, "hasViews");
        AbstractJClass notifierClass = getJClass(OnViewChangedNotifier.class);
        getInitBodyInjectionBlock().staticInvoke(notifierClass, "registerOnViewChangedListener").arg(_this());
    }

    /**
     * implementBeanHolder
     */
    protected void implementBeanHolder() {
        getGeneratedClass()._implements(BeanHolder.class);
        JDirectClass genericType = getCodeModel().directClass(STRING_T);

        JFieldVar beansField = getGeneratedClass().field(PRIVATE | FINAL,
            getClasses().MAP.narrow(getClasses().CLASS.narrowAny(), getClasses().OBJECT), "beans_",
            _new(getClasses().HASH_MAP.narrow(getClasses().CLASS.narrowAny(), getClasses().OBJECT)));

        JMethod getBeanMethod = getGeneratedClass().method(PUBLIC, genericType, "getBean");
        getBeanMethod.generify(STRING_T);
        getBeanMethod.annotate(Override.class);

        JVar keyParam = getBeanMethod.param(getClasses().CLASS.narrow(genericType), "key");
        getBeanMethod.body()._return(cast(genericType, beansField.invoke("get").arg(keyParam)));

        JMethod putBeanMethod = getGeneratedClass().method(PUBLIC, getCodeModel().VOID, "putBean");
        putBeanMethod.generify(STRING_T);
        putBeanMethod.annotate(Override.class);

        keyParam = putBeanMethod.param(getClasses().CLASS.narrow(genericType), "key");
        JVar valueParam = putBeanMethod.param(genericType, "value");
        putBeanMethod.body().add(beansField.invoke("put").arg(keyParam).arg(valueParam));
    }

    /**
     * findViewById
     *
     * @param idRef idRef
     * @return findViewById
     */
    public JInvocation findViewById(JFieldRef idRef) {
        JInvocation findViewById = invoke(getOnViewChangedHasViewsParam(), "internalFindViewById");
        findViewById.arg(idRef);
        return findViewById;
    }

    /**
     * getFoundViewHolder
     *
     * @param idRef idRef
     * @param viewClass viewClass
     * @return getFoundViewHolder
     */
    public FoundViewHolder getFoundViewHolder(JFieldRef idRef, AbstractJClass viewClass) {
        return getFoundViewHolder(idRef, viewClass, null);
    }

    /**
     * getFoundViewHolder
     *
     * @param idRef idRef
     * @param viewClass viewClass
     * @param fieldRef fieldRef
     * @return foundViewHolder
     */
    public FoundViewHolder getFoundViewHolder(JFieldRef idRef, AbstractJClass viewClass, IJAssignmentTarget fieldRef) {
        String idRefString = idRef.name();
        FoundViewHolder foundViewHolder = (FoundViewHolder) foundHolders.get(idRefString);
        if (foundViewHolder == null) {
            foundViewHolder = createFoundViewAndIfNotNullBlock(idRef, viewClass, fieldRef);
            foundHolders.put(idRefString, foundViewHolder);
        }
        return foundViewHolder;
    }

    /**
     * createFoundViewAndIfNotNullBlock
     *
     * @param idRef idRef
     * @param viewClass viewClass
     * @param fieldRef fieldRef
     * @return FoundViewHolder
     */
    protected FoundViewHolder createFoundViewAndIfNotNullBlock(JFieldRef idRef,
        AbstractJClass viewClass, IJAssignmentTarget fieldRef) {
        IJExpression findViewExpression = findViewById(idRef);
        JBlock block = getOnViewChangedBodyBeforeInjectionBlock();

        if (viewClass == null) {
            viewClass = getClasses().COMPONENT;
        }

        IJAssignmentTarget foundView = fieldRef;
        if (foundView == null) {
            JVar view = block.decl(viewClass, "view_" + idRef.name(), findViewExpression);
            if (viewClass.isParameterized()) {
                codeModelHelper.addSuppressWarnings(view, "unchecked");
            }
            foundView = view;
        } else {
            block.add(foundView.assign(findViewExpression));
        }
        return new FoundViewHolder(this, viewClass, foundView, getOnViewChangedBodyViewHolderBlock());
    }

    /**
     * getFractionAbility
     *
     * @return getClasses
     */
    private AbstractJClass getFractionAbility() {
        Elements elementUtils = getProcessingEnvironment().getElementUtils();
        if (elementUtils.getTypeElement(CanonicalNameConstants.OHOSX_FRAGMENT_ABILITY) != null) {
            return getClasses().OHOSX_FRAGMENT_ABILITY;
        } else {
            return getClasses().FRAGMENT_ABILITY;
        }
    }

    /**
     * getFindNativeFragmentByTag
     *
     * @return findNativeFragmentByTag
     */
    public JMethod getFindNativeFragmentByTag() {
        if (findNativeFragmentByTag == null) {
            setFindNativeFragmentByTag();
        }
        return findNativeFragmentByTag;
    }

    /**
     * setFindNativeFragmentByTag
     */
    protected void setFindNativeFragmentByTag() {
        findNativeFragmentByTag = getGeneratedClass()
            .method(PRIVATE, getClasses().FRACTION, "findNativeFragmentByTag");
        JVar tagParam = findNativeFragmentByTag.param(getClasses().STRING, "tag");

        JBlock body = findNativeFragmentByTag.body();

        body._if(getContextRef()._instanceof(getClasses()
            .ABILITY).not())._then()._return(_null());

        JVar abilityVar = body.decl(getClasses().ABILITY,
            "ability_", cast(getClasses().ABILITY, getContextRef()));

        body._return(abilityVar.invoke("getFractionManager")
            .invoke("getFractionByTag").arg(tagParam));
    }

    /**
     * getTextWatcherHolder
     *
     * @param idRef idRef
     * @param viewParameterType viewParameterType
     * @return textObserverHolder
     */
    public TextObserverHolder getTextWatcherHolder(JFieldRef idRef, TypeMirror viewParameterType) {
        String idRefString = idRef.name();
        TextObserverHolder textObserverHolder = textObserverHolders.get(idRefString);
        if (textObserverHolder == null) {
            textObserverHolder = createTextObserverHolder(idRef, viewParameterType);
            textObserverHolders.put(idRefString, textObserverHolder);
        }
        return textObserverHolder;
    }

    /**
     * createTextObserverHolder
     *
     * @param idRef idRef
     * @param viewParameterType viewParameterType
     * @return TextObserverHolder
     */
    private TextObserverHolder createTextObserverHolder(JFieldRef idRef, TypeMirror viewParameterType) {
        JDefinedClass onTextChangeListenerClass = getCodeModel().anonymousClass(getClasses().TEXT_OBSERVER);
        AbstractJClass viewClass = getClasses().TEXT_VIEW;
        if (viewParameterType != null) {
            viewClass = getJClass(viewParameterType.toString());
        }

        JBlock body = getOnViewChangedBodyInjectionBlock().blockSimple();
        JVar viewVariable = body.decl(FINAL, viewClass,
            "view", cast(viewClass, findViewById(idRef)));
        body._if(viewVariable.ne(JExpr._null()))._then()
            .invoke(viewVariable, "addTextObserver").arg(_new(onTextChangeListenerClass));

        return new TextObserverHolder(this, viewVariable, onTextChangeListenerClass);
    }

    /**
     * ListenerHolder
     *
     * @param idRef idRef
     * @return onSliderProgressUpdatedListenerHolder
     */
    public OnSliderProgressUpdatedListenerHolder getOnSliderProgressUpdatedListenerHolder(JFieldRef idRef) {
        String idRefString = idRef.name();
        OnSliderProgressUpdatedListenerHolder onSliderProgressUpdatedListenerHolder
            = onSliderProgressUpdatedListenerHolders.get(idRefString);
        if (onSliderProgressUpdatedListenerHolder == null) {
            onSliderProgressUpdatedListenerHolder = createOnSliderProgressUpdatedListenerHolder(idRef);
            onSliderProgressUpdatedListenerHolders.put(idRefString, onSliderProgressUpdatedListenerHolder);
        }
        return onSliderProgressUpdatedListenerHolder;
    }

    /**
     * ListenerHolder
     *
     * @param idRef idRef
     * @return OnSliderProgressUpdatedListenerHolder
     */
    private OnSliderProgressUpdatedListenerHolder createOnSliderProgressUpdatedListenerHolder(JFieldRef idRef) {
        JDefinedClass onSliderChangeListenerClass = getCodeModel()
            .anonymousClass(getClasses().ON_SLIDER_CHANGE_LISTENER);
        AbstractJClass viewClass = getClasses().SLIDER;

        FoundViewHolder foundViewHolder = getFoundViewHolder(idRef, viewClass);
        foundViewHolder.getIfNotNullBlock()
            .invoke(foundViewHolder.getRef(), "setValueChangedListener")
            .arg(_new(onSliderChangeListenerClass));

        return new OnSliderProgressUpdatedListenerHolder(this, onSliderChangeListenerClass);
    }

    /**
     * getPageChangeHolder
     *
     * @param idRef idRef
     * @param viewParameterType viewParameterType
     * @param hasAddOnPageChangeListenerMethod hasAddOnPageChangeListenerMethod
     * @return pageChangeHolder
     */
    public PageChangeHolder getPageChangeHolder(JFieldRef idRef, TypeMirror viewParameterType,
        boolean hasAddOnPageChangeListenerMethod) {
        String idRefString = idRef.name();
        PageChangeHolder pageChangeHolder = pageChangeHolders.get(idRefString);
        if (pageChangeHolder == null) {
            pageChangeHolder = createPageChangeHolder(idRef, viewParameterType, hasAddOnPageChangeListenerMethod);
            pageChangeHolders.put(idRefString, pageChangeHolder);
        }
        return pageChangeHolder;
    }

    /**
     * createPageChangeHolder
     *
     * @param idRef idRef
     * @param viewParameterType viewParameterType
     * @param hasAddOnPageChangeListenerMethod hasAddOnPageChangeListenerMethod
     * @return PageChangeHolder
     */
    private PageChangeHolder createPageChangeHolder(JFieldRef idRef,
        TypeMirror viewParameterType, boolean hasAddOnPageChangeListenerMethod) {
        AbstractJClass viewClass;
        JDefinedClass onPageChangeListenerClass;
        if (getProcessingEnvironment().getElementUtils()
            .getTypeElement(CanonicalNameConstants.OHOSX_VIEW_PAGER) == null) {
            viewClass = getClasses().VIEW_PAGER;
            onPageChangeListenerClass = getCodeModel().anonymousClass(getClasses().PAGE_CHANGE_LISTENER);
        } else {
            viewClass = getClasses().OHOSX_VIEW_PAGER;
            onPageChangeListenerClass = getCodeModel().anonymousClass(getClasses().OHOSX_PAGE_CHANGE_LISTENER);
        }
        if (viewParameterType != null) {
            viewClass = getJClass(viewParameterType.toString());
        }
        JBlock blockSimple = getOnViewChangedBodyInjectionBlock().blockSimple();
        JVar viewVariable = blockSimple.decl(FINAL, viewClass,
            "view", cast(viewClass, findViewById(idRef)));
        JBlock block = blockSimple._if(viewVariable.ne(JExpr._null()))._then();
        if (hasAddOnPageChangeListenerMethod) {
            block.invoke(viewVariable, "addPageChangedListener").arg(_new(onPageChangeListenerClass));
        } else {
            block.invoke(viewVariable, "addPageChangedListener").arg(_new(onPageChangeListenerClass));
        }
        return new PageChangeHolder(this, viewVariable, onPageChangeListenerClass);
    }

    /**
     * getOnKeyDownSwitchBody
     *
     * @return keyEventCallbackMethodsDelegate
     */
    @Override
    public JSwitch getOnKeyDownSwitchBody() {
        return keyEventCallbackMethodsDelegate.getOnKeyDownSwitchBody();
    }

    /**
     * getOnKeyDownKeyEventParam
     *
     * @return keyEventCallbackMethodsDelegate
     */
    @Override
    public JVar getOnKeyDownKeyEventParam() {
        return keyEventCallbackMethodsDelegate.getOnKeyDownKeyEventParam();
    }

    /**
     * getOnKeyLongPressSwitchBody
     *
     * @return keyEventCallbackMethodsDelegate
     */
    @Override
    public JSwitch getOnKeyLongPressSwitchBody() {
        return keyEventCallbackMethodsDelegate.getOnKeyPressAndHoldSwitchBody();
    }

    /**
     * getOnKeyLongPressKeyEventParam
     *
     * @return keyEventCallbackMethodsDelegate
     */
    @Override
    public JVar getOnKeyLongPressKeyEventParam() {
        return keyEventCallbackMethodsDelegate.getOnKeyPressAndHoldKeyEventParam();
    }

    /**
     * getOnKeyUpSwitchBody
     *
     * @return keyEventCallbackMethodsDelegate
     */
    @Override
    public JSwitch getOnKeyUpSwitchBody() {
        return keyEventCallbackMethodsDelegate.getOnKeyUpSwitchBody();
    }

    /**
     * getOnKeyUpKeyEventParam
     *
     * @return keyEventCallbackMethodsDelegate
     */
    @Override
    public JVar getOnKeyUpKeyEventParam() {
        return keyEventCallbackMethodsDelegate.getOnKeyUpKeyEventParam();
    }

    /**
     * getDataBindingField
     *
     * @return dataBindingDelegate
     */
    public JFieldVar getDataBindingField() {
        return dataBindingDelegate.getDataBindingField();
    }

    /**
     * getDataBindingInflationExpression
     *
     * @param contentViewId contentViewId
     * @param container container
     * @param attachToRoot attachToRoot
     * @return dataBindingDelegate
     */
    public IJExpression getDataBindingInflationExpression(IJExpression contentViewId,
        IJExpression container, boolean attachToRoot) {
        return dataBindingDelegate
            .getDataBindingInflationExpression(contentViewId, container, attachToRoot);
    }
}
